#include "Map.h"
#include "SDL.h"
#include "SDL_opengl.h"
#include "SDL_image.h"
#include <iostream>
#include <fstream>
#include <cmath>
#include <string>

using namespace std;
typedef vector<vector<short> > LayerData;



void printBinary(char n)
{
	string s;
	for (int i = 0; i < 8; i++){
		char result = 1 & n;
		if (result)
		{
			s.push_back('1');
		}
		else
		{
			s.push_back('0');
		}
		n = n >> 1;
	}
	
	for (string::reverse_iterator it = s.rbegin(); it != s.rend(); it++)
	{
		cout << (*it);
	}
	cout << endl;
}

short readShort(ifstream& mapFile)
{	
	short r = 0;
	char buffer[2];
	
	mapFile.read(buffer, 2);
	r = r | (unsigned char)buffer[0];
	r = r << 8;
	
	r = r | (unsigned char)buffer[1];
	//printBinary(buffer[1]);
	//cout << r << endl;
	return r;
}

auto_ptr<LayerData> Map::loadLayer(ifstream& mapFile)
{
	auto_ptr<LayerData> mapLayer(new LayerData(numRows));
	for (int i = 0; i < numRows; i++)
	{
		for (int j = 0; j < numCols; j++)
		{
			(*mapLayer)[i].push_back(readShort(mapFile));
		}
	}
	return mapLayer;
}

Map::Map(const string& mapFileName, int x_size, int y_size): xSize(x_size), ySize(y_size), xMax(x_size), yMax(y_size)
{
	loadMap(mapFileName);
	
	SDL_Surface* tileset = IMG_Load("blocks1.png");
	if (!tileset)
		cout << "failed to load tileset" << endl;
	
	tilesetHeight = tileset->h;
	tilesetWidth = tileset->w;
	
	tileCols = ((tileset->w) - (2 * tileMargin) + tileSpacing) / (tileWidth + tileSpacing);
	tileRows = ( (tileset->h) - (2 * tileMargin) + tileSpacing) / (tileHeight + tileSpacing);

	glGenTextures(1, &texture);
	
	glBindTexture(GL_TEXTURE_2D, texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,tileset->w,tileset->h,0,GL_RGBA,GL_UNSIGNED_BYTE,tileset->pixels);
	
	SDL_FreeSurface(tileset);
}

Map::~Map()
{
	glDeleteTextures(1, &texture);
}

void Map::loadMap(const string& mapFileName)
{
	ifstream mapFile;	
	cout << mapFileName << endl;
	mapFile.open(mapFileName.c_str());
	if (mapFile.fail())
	{
		cout << "Error loading map: " << mapFileName << endl;
		throw "ERROR";
	}
	
	numRows = readShort(mapFile);
	numCols = readShort(mapFile);
	tileHeight = readShort(mapFile);
	tileWidth = readShort(mapFile);
	tileSpacing = readShort(mapFile);
	tileMargin = readShort(mapFile);
	
	worldLayer = loadLayer(mapFile);
	collisionLayer = loadLayer(mapFile);
	
	mapFile.close();
}

bool Map::outOfBounds(int x, int y) const
{
	return (x < 0 || x >= numCols || y < 0 || y >= numRows);
}

void Map::draw()
{
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable (GL_BLEND);
	//glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glBindTexture(GL_TEXTURE_2D, texture);
	for (int x = floor(xMax - xSize); x < ceil(xMax); x++)
	{
		for (int y = floor(yMax - ySize); y < ceil(yMax); y++)
		{
			if ( outOfBounds(x,y) )
				continue;
			
			GLfloat s,t;
			s = t = 0;
			
			short tileId = (*worldLayer)[numRows - y - 1][x];
			if (tileId == 0) continue;
			
			// The "- 1" is to compensate for the ids starting at 1, not 0
			tileId -= 1;
			
						
			short row = tileId / tileCols;
			short col = tileId - (tileCols * row);
			
			
			int pixelX = tileMargin + ( col * (tileSpacing + tileWidth) );
			int pixelY = tileMargin + ( (tileRows - row - 1) * (tileSpacing + tileHeight) );
			
			s = ( (GLfloat)pixelX ) / tilesetWidth;
			t = ( (GLfloat)pixelY ) / tilesetHeight;

			
			GLfloat unitS = ( (GLfloat)tileWidth ) / tilesetWidth;
			GLfloat unitT = ( (GLfloat)tileHeight ) / tilesetHeight;

			
			glBegin(GL_QUADS);
			glTexCoord2f(s, t);					glVertex2f(x, y);
			glTexCoord2f(s, t + unitT);			glVertex2f(x, y+1);
			glTexCoord2f(s + unitS, t + unitT); glVertex2f(x+1, y+1);
			glTexCoord2f(s + unitS, t);			glVertex2f(x+1, y);
			glEnd();
		}
	}
	
	glDisable(GL_BLEND);
	//glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

void Map::update()
{
}

void Map::handleEvent(SDL_Event* event)
{
}

bool Map::collision(const Collidable& object) const
{
	Vector2 pos = object.getPosition();
	Rect size = object.getSize();
	
	float posX = pos.x;
	float posY = pos.y;
	float height = size.h;
	float width = size.w;
	
	
	int bottom = floor(posY);
	int top = ceil(posY + height);
	int left = floor(posX);
	int right = floor(posX + width);
	
	if ( outOfBounds(left,bottom) || outOfBounds(right,top) )
	{
		return true;
	}
	
	int firstRow = numRows - top - 1;
	int lastRow = numRows - bottom;
	int lastCol = right + 1;
	
	for (int r = firstRow; r < lastRow; r++)
	{
		for (int c = left; c < lastCol; c++)
		{
			if( (*collisionLayer)[r][c] > 0 )
			{
				return true;
			}
		}
	}
	return false;
}

int Map::getViewWidth() const
{
	return xSize;
}

int Map::getViewHeight() const
{
	return ySize;
}

void Map::setMaxBounds(float x, float y)
{
	xMax = x;
	yMax = y;
}
